package com.crl.rcdp.gateway.filter;

import com.cars.util.auth.AuthDTO;
import com.cars.util.date.DateUtil;
import com.cars.util.global.GlobalReturnCode;
import com.cars.util.global.StatusEnum;
import com.cars.util.ip.IPUtil;
import com.cars.util.json.JsonResultUtil;
import com.cars.util.json.JsonUtil;
import com.cars.util.jwt.JwtResult;
import com.cars.util.jwt.JwtUtil;
import com.cars.util.string.StringUtil;
import com.crl.rcdp.sys.service.SysAppService;
import com.crl.rcdp.sys.service.SysLogService;
import com.crl.rcdp.sys.service.SysRoleService;
import com.crl.rcdp.sys.service.SysUserService;
import com.crl.redis.RedisKey;
import com.crl.redis.RedisUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * @description: 校验权限
 * @author: 孔垂云
 * @version: 1.0
 * @date: 2020/8/10 23:25
 */
public class TokenFilter extends ZuulFilter {
    @Autowired
    private SysLogService sysLogService;
    @Value("${gatewayPermitUrl}")
    private String permitUrl;//定义不需要校验的url
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysRoleService sysRoleService;
    @Autowired
    private SysAppService sysAppService;
    /**
     * 存储无需鉴权的请求列表
     */
    private ArrayList<String> permitUrlList = null;

    /**
     * 将通配符表达式转化为正则表达式
     *
     * @param path
     * @return
     */
    private String getRegPath(String path) {
        char[] chars = path.toCharArray();
        int len = chars.length;
        StringBuilder sb = new StringBuilder();
        boolean preX = false;
        for (int i = 0; i < len; i++) {
            if (chars[i] == '*') {//遇到*字符
                if (preX) {//如果是第二次遇到*，则将**替换成.*
                    sb.append(".*");
                    preX = false;
                } else if (i + 1 == len) {//如果是遇到单星，且单星是最后一个字符，则直接将*转成[^/]*
                    sb.append("[^/]*");
                } else {//否则单星后面还有字符，则不做任何动作，下一把再做动作
                    preX = true;
                    continue;
                }
            } else {//遇到非*字符
                if (preX) {//如果上一把是*，则先把上一把的*对应的[^/]*添进来
                    sb.append("[^/]*");
                    preX = false;
                }
                if (chars[i] == '?') {//接着判断当前字符是不是?，是的话替换成.
                    sb.append('.');
                } else {//不是?的话，则就是普通字符，直接添进来
                    sb.append(chars[i]);
                }
            }
        }
        return sb.toString();
    }

    /**
     * 判断请求是否是直接路由请求
     *
     * @param url 请求url
     * @return true：直接路由
     */
    private boolean isPermit(String url) {
        if (permitUrlList == null) {
            permitUrlList = new ArrayList<>();
            Collections.addAll(permitUrlList, permitUrl.split(","));
            int i = 0;
            for (String rule : permitUrlList) {//把url转换成正则通配字符串
                rule = getRegPath(rule);
                permitUrlList.set(i, rule);
                i++;
            }
        }

        for (String rule : permitUrlList) {
            if (Pattern.compile(rule).matcher(url).matches()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 校验方式
     * 1、先校验该url是否可以直接请求
     * 2、再判断Authorization是否为空，如果不为空，校验jwt
     * 3、判断accessToken是否为空，如果不为空，校验jwt
     * 4、判断appid是否为空，如果不为空，校验appid
     *
     * @return
     */
    @Override
    public Object run() {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        //共有三种校验方式，appid
        //判断是否需要校验权限
        String servletPath = request.getServletPath();//获取请求url
        System.out.println("请求url:" + servletPath);
        // 无需鉴权
        if (isPermit(servletPath)) {
            return null;
        }
        String authorization = request.getHeader("Authorization");//获取authorization
        String appid = request.getHeader("appid");//appid
        String accessToken = request.getHeader("accessToken");//accessToken
        if (io.netty.util.internal.StringUtil.isNullOrEmpty(authorization) && io.netty.util.internal.StringUtil.isNullOrEmpty(appid) && io.netty.util.internal.StringUtil.isNullOrEmpty(accessToken)) {
            //未授权
            this.returnUnAuth(currentContext, GlobalReturnCode.NO_AUTHORIZATION);
            return null;
        }
        //按照authorization进行校验
        if (StringUtils.isNotBlank(authorization)) {
            //校验jwt
            JwtResult jwtResult = JwtUtil.parseJWT(authorization);
            boolean isValid = jwtResult.isValid();
            // 校验accessToken是否有效
            if (!isValid) {
                this.returnUnAuth(currentContext, GlobalReturnCode.INVALID_TOKEN);
                return null;
            }
            //校验权限
            AuthDTO authDTO = redisUtil.get(RedisKey.AUTH_PASSWORD_KEY + JwtUtil.getJWTOwner(authorization), AuthDTO.class);
            if (authDTO == null) {//redis不存在
                this.returnUnAuth(currentContext, GlobalReturnCode.INVALID_TOKEN);
                return null;
            }
            //url不在权限内
            if (!this.checkRolesContaisPath(authDTO.getListRoles(), servletPath)) {
                this.returnUnAuth(currentContext, GlobalReturnCode.NO_PERMISSION_TOKEN);
                return null;
            }
            return null;
        }
        //按照accessToken进行校验
        if (StringUtils.isNotBlank(accessToken)) {
            JwtResult jwtResult = JwtUtil.parseJWT(accessToken);
            boolean isValid = jwtResult.isValid();
            // 校验accessToken是否有效
            if (!isValid) {
                //token不合法
                this.returnUnAuth(currentContext, GlobalReturnCode.INVALID_TOKEN);
                return null;
            }
            //从redis根据owner获取用户的基本信息，包括权限，是否过期这些
            AuthDTO authDTO = redisUtil.get(RedisKey.AUTH_ACCESSTOKEN_KEY + JwtUtil.getJWTOwner(accessToken), AuthDTO.class);
            if (authDTO == null) {//redis不存在
                this.returnUnAuth(currentContext, GlobalReturnCode.INVALID_TOKEN);
                return null;
            }
            //判断是否已过期
            if (authDTO.getValidDate().compareTo(DateUtil.getSystemDate()) < 0) {
                this.returnUnAuth(currentContext, GlobalReturnCode.EXPIRED);
                return null;
            }
            //判断app是否禁用
            if (authDTO.getStatus().equals(StatusEnum.DISABLED.getValue())) {
                this.returnUnAuth(currentContext, GlobalReturnCode.DISABLED);
                return null;
            }

            //url不在权限内
            if (!authDTO.getUrls().containsKey(servletPath)) {
                returnUnAuth(currentContext, GlobalReturnCode.NO_PERMISSION_TOKEN);
                return null;
            }
//            //判断权限
//            if (!checAppidContainsPath(jwtResult.getOwner(), servletPath)) {
//
//            }
            return null;
        }
        //按照appid进行校验
        if (StringUtils.isNotBlank(appid)) {
            String ip = StringUtil.getIp(request);
            AuthDTO authDTO = redisUtil.get(RedisKey.AUTH_APPID_KEY + appid, AuthDTO.class);
            if (authDTO == null) {//redis不存在
                authDTO = sysAppService.getAuthDTOByAppid(appid);
                if (StringUtil.isNullOrEmpty(authDTO.getValidDate())) {
                    this.returnUnAuth(currentContext, GlobalReturnCode.INVALID_APPID);
                    return null;
                }
            }
            if (ip.equals("0:0:0:0:0:0:0:1")) {
                ip = "127.0.0.1";
            }
            //判断ip
            if (!IPUtil.isValidIp(ip, authDTO.getIps())) {//TODO完善校验ip的方法
                returnUnAuth(currentContext, GlobalReturnCode.IP_INVALID);
                return null;
            }
            //判断用户已过期
            if (authDTO.getValidDate().compareTo(DateUtil.getSystemDate()) < 0) {
                this.returnUnAuth(currentContext, GlobalReturnCode.EXPIRED);
                return null;
            }
            //判断app是否禁用
            if (authDTO.getStatus() != null && authDTO.getStatus().equals(StatusEnum.DISABLED.getValue())) {
                this.returnUnAuth(currentContext, GlobalReturnCode.DISABLED);
                return null;
            }

            //url不在权限内
            if (!authDTO.getUrls().containsKey(servletPath)) {
                this.returnUnAuth(currentContext, GlobalReturnCode.NO_PERMISSION_TOKEN);
                return null;
            }
            return null;
        }
        return null;
    }

    /**
     * 返回未授权
     *
     * @param
     */
    private void returnUnAuth(RequestContext context, GlobalReturnCode globalReturnCode) {
        HttpServletResponse response = context.getResponse();
        try {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            String body = JsonUtil.toStr(JsonResultUtil.error(globalReturnCode));
            response.setContentType(MediaType.APPLICATION_JSON.getType());
            response.getOutputStream().write(body.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 判断角色列表是否含有对应的url
     *
     * @param listRole 角色列表
     * @param path
     * @return
     */
    private boolean checkRolesContaisPath(List<Integer> listRole, String path) {
        for (Integer roleId : listRole) {
            HashMap<String, String> hashRolePath = redisUtil.get(RedisKey.AUTH_ROLE_PATH + roleId, HashMap.class);
            if (hashRolePath == null) {
                hashRolePath = sysRoleService.listRolePaths(roleId);
                redisUtil.set(RedisKey.AUTH_ROLE_PATH + roleId, hashRolePath);
            }
            if (hashRolePath.containsKey(path)) {
                return true;
            }
        }
        return false;
    }
}
